package com.joshjung.hover
{
    import com.joshjung.collections.LLElement;
    import com.joshjung.collections.LLEvent;
    import com.joshjung.collections.LLIterator;
    import com.joshjung.collections.LinkedList;
    import com.joshjung.echosurf.event.ESCAuthenticateEvent;
    import com.joshjung.echosurf.event.ESJoinEvent;
    import com.joshjung.echosurf.event.ESLeaveEvent;
    import com.joshjung.echosurf.event.ESPacketEvent;
    import com.joshjung.hover.functional.Viewport;
    import com.joshjung.hover.interactive.HoverCraft;
    import com.joshjung.hover.interactive.PhysicObject;
    import com.joshjung.hover.interfaces.IByteArrayable;
    import com.joshjung.hover.interfaces.IKeyboardResponder;
    import com.joshjung.hover.interfaces.ILevel;
    import com.joshjung.hover.interfaces.ILoopable;
    import com.joshjung.hover.levels.Level1;
    import com.joshjung.hover.levels.Level2;
    import com.joshjung.hover.net.HoverSocket;

    import flash.display.DisplayObject;
    import flash.display.FrameLabel;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.system.System;
    import flash.text.TextField;
    import flash.ui.Keyboard;
    import flash.utils.Dictionary;
    import flash.utils.setTimeout;

    [SWF(height = "600", width = "600", frameRate = "40", backgroundColor = "0xEEEEEE")]
    /**
     *
     * @author jjung
     */
    public class Hover extends Sprite
    {
        //---------------------------------------------------------------------
        //
        //  Static Variables
        //
        //---------------------------------------------------------------------
        public static const COLORS : Array = [0xFF0000, 0x00FF00, 0x0000FF, 0x660000, 0x006600, 0x000066, 0x330000, 0x003300, 0x000033, 0xEEEEEE, 0x444444, 0x666600, 0x006666, 0x996633, 0x336699, 0x663399,
            0x993366];

        //---------------------------------------------------------------------
        //
        //  Variables
        //
        //---------------------------------------------------------------------
        public var canvas : Sprite;
        public var viewport : Viewport;

        public var lastTime : Number = 0;
        public var startTime : Number = 0;

        public var hoverCraft : HoverCraft;
        public var networkPlayers : LinkedList;

        public var loopList : LinkedList;

        protected var textField : TextField;

        protected var levels : Array = [new Level1()];
        protected var curLevel : ILevel;
        protected var curLevelIx : int = -1;

        protected var done : Boolean = false;
        protected var started : Boolean = false;

        protected var socket : HoverSocket;

        protected var keyboardResponders : LinkedList;

        //---------------------------------------------------------------------
        //
        //  Constructor
        //
        //---------------------------------------------------------------------
        /**
         *  Constructor
         */
        public function Hover()
        {
            //---------------------------
            // Setup the linked lists
            //---------------------------

            loopList = new LinkedList();
            loopList.addEventListener(LLEvent.INSERT, loopList_insertHandler);
            loopList.addEventListener(LLEvent.REMOVE, loopList_removeHandler);

            networkPlayers = new LinkedList();

            keyboardResponders = new LinkedList();

            //---------------------------
            // Setup viewport
            //---------------------------
            viewport = new Viewport();
            viewport.left = viewport.top = 0;
            viewport.right = 600;
            viewport.bottom = 600;

            //---------------------------
            // Setup the keyboard
            //---------------------------
            stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
            stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);

            //---------------------------
            // Setup the interface
            //---------------------------
            textField = new TextField();
            addChild(textField);

            textField.x = 0;
            textField.y = 0;
            textField.width = viewport.width;
            textField.height = 100;

            //---------------------------
            // Setup the network
            //---------------------------
            socket = new HoverSocket("50.56.122.53", 1002);
            socket.addEventListener(ESPacketEvent.PACKET_RECEIVED, packetReceivedHandler);
            socket.addEventListener(ESLeaveEvent.PEER_LEAVE, peerDeathHandler);
            socket.addEventListener(ESJoinEvent.PEER_JOIN, peerJoinHandler);
            socket.addEventListener(ESCAuthenticateEvent.AUTHENTICATE, authenticateHandler);

            socket.connect();

            //---------------------------
            // Start the first level
            //---------------------------
            nextLevel();
        }

        //---------------------------------------------------------------------
        //
        //  Methods
        //
        //---------------------------------------------------------------------
        protected function startLevel()
        {
            started = true;
            done = false;
            startTime = new Date().time;
        }

        protected function nextLevel()
        {
            loopList.clear();

            if (canvas)
            {
                canvas.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
                removeChild(canvas);
            }

            canvas = new Sprite();
            canvas.x = 0;
            canvas.y = 0;
            canvas.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
            addChild(canvas);

            hoverCraft = new HoverCraft(viewport, loopList);
            hoverCraft.x = viewport.width / 2;
            hoverCraft.y = viewport.height / 2;
            hoverCraft.clientID = socket.clientID;

            loopList.insert(hoverCraft);

            started = false;
            done = false;

            curLevelIx++;

            if (curLevelIx >= levels.length)
            {
                curLevelIx = 0;
            }

            curLevel = levels[curLevelIx];

            curLevel.initialize(this);

            loopList.insert(curLevel);
        }

        protected function enterFrameHandler(event : Event) : void
        {
            hoverCraft.clientID = socket.clientID;
            
            if (done)
            {
                return;
            }

            if (!lastTime)
            {
                lastTime = new Date().time;
                return;
            }

            var curTime : Number = new Date().time;
            var elapsed : Number = (curTime - lastTime) / 1000;

            hoverCraft.color = COLORS[socket.clientID];

            var iterator : LLIterator = new LLIterator(loopList);
            var loopable : ILoopable;

            while (loopable = iterator.next())
            {
                loopable.loop(elapsed);
            }

            done = curLevel.checkWin();

            lastTime = new Date().time;

            if (started)
            {
                var totalTime : Number = (curTime - startTime) / 1000;
                textField.text = "Time: " + (totalTime).toString(10);

                if (!isNaN(curLevel.bestTime))
                {
                    textField.text += "\nBest Time: " + curLevel.bestTime.toString(10);
                }

                if (done && (isNaN(curLevel.bestTime) || curLevel.bestTime > totalTime))
                {
                    curLevel.bestTime = totalTime;
                }

                if (done)
                {
                    setTimeout(function() : void
                    {
                        nextLevel()
                    }, 1000);
                }
            }

            if (socket.isServer)
            {
                socket.serverSendAll(loopList);
            }
        }

        //---------------------------------------------------------------------
        //
        //  Events
        //
        //---------------------------------------------------------------------
        protected function loopList_insertHandler(event : LLEvent) : void
        {
            if (event.data is PhysicObject && !canvas.contains(event.data))
            {
                canvas.addChild(event.data as DisplayObject);
            }
        }

        protected function loopList_removeHandler(event : LLEvent) : void
        {
            if (event.data is PhysicObject && canvas.contains(event.data))
            {
                canvas.removeChild(event.data as DisplayObject);
            }
        }

        protected function keyDownHandler(event : KeyboardEvent) : void
        {
            if (event.charCode == 114)
            {
                nextLevel();

                return;
            }

            if (event.charCode >= 49 && event.charCode <= 57)
            {
                curLevelIx = event.charCode - 49;

                nextLevel();

                return;
            }

            if (!started)
            {
                startLevel();
            }

            var iterator : LLIterator = new LLIterator(keyboardResponders);
            var data : IKeyboardResponder;

            while (data = iterator.next())
            {
                data.keyDown(event.keyCode);
            }
        }

        protected function keyUpHandler(event : KeyboardEvent) : void
        {
            var iterator : LLIterator = new LLIterator(keyboardResponders);
            var data : IKeyboardResponder;

            while (data = iterator.next())
            {
                data.keyUp(event.keyCode);
            }
        }

        protected function packetReceivedHandler(event : ESPacketEvent) : void
        {
            if (!socket.isServer)
            {
                var serverCheck : int = event.packet.data.readByte();
                
                if (serverCheck != HoverSocket.SERVER_SIGNATURE)
                {
                    trace("SERVER SIGNATURE CHECK INVALID.");
                    return;
                }
                
                while (event.packet.data.bytesAvailable)
                {
                    var clientID : int = event.packet.data.readByte();

                    var byteArrayable : IByteArrayable = loopList.find(clientID, "clientID");

                    byteArrayable.fromBA(event.packet.data);
                }
            }
            else
            {
                
                while (event.packet.data.bytesAvailable)
                {
                    var peerCheck : int = event.packet.data.readByte();

                    if (peerCheck != HoverSocket.PEER_SIGNATURE)
                    {
                        trace("PEER SIGNATURE CHECK INVALID.");
                        return;
                    }

                    var clientID : int = event.packet.clientID;
                    var eventType : int = event.packet.data.readByte();
                    var key : int = event.packet.data.readInt();
                    var keyboardResponder : IKeyboardResponder = loopList.find(clientID, "clientID");

                    if (eventType == HoverSocket.KEYDOWN_BYTE)
                    {
                        keyboardResponder.keyDown(key);
                    }
                    else if (eventType == HoverSocket.KEYUP_BYTE)
                    {
                        keyboardResponder.keyUp(key);
                    }
                }
            }
        }

        protected function authenticateHandler(event : ESCAuthenticateEvent) : void
        {
            hoverCraft.clientID = socket.clientID;

            if (socket.isServer)
            {
                // We are the server, so just pass keyboard events directly to the 
                // hovercraft
                keyboardResponders.insert(hoverCraft);

                trace("Connected as server!");
            }
            else
            {
                // We are a client, so pass our keyboard events directly to our server via the socket.
                keyboardResponders.insert(socket);

                trace("Connected as peer!");
            }
        }

        protected function peerDeathHandler(event : ESLeaveEvent) : void
        {
            var hc : HoverCraft = networkPlayers.find(event.clientID, "clientID");

            loopList.remove(hc);
            networkPlayers.remove(hc);

            trace("-- Peer died: " + event.clientID);
        }

        protected function peerJoinHandler(event : ESJoinEvent) : void
        {
            var hc : HoverCraft = new HoverCraft(viewport, loopList);
            hc.clientID = event.clientID;
            hc.x = viewport.width / 2;
            hc.y = viewport.height / 2;

            loopList.insert(hc);
            networkPlayers.insert(hc);

            trace("++ Peer join: " + event.clientID);
        }
    }
}
